<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    
    <title>FragmentStatePagerAdapter在ViewPager中优化了什么 | ApocalypseBlog</title>
    <canvas id="header_canvas"style="position:absolute;bottom:0"></canvas> 
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    

    

    

    

    
<link rel="stylesheet" href="/dist/build.css?v=1.14.0.css">

    <script src="/javascripts/bubble.js"></script>
    <script>
        window.isPost = true
        window.aomori = {
            
            
        }
        window.aomori_logo_typed_animated = false
        window.aomori_search_algolia = false

    </script>
<script>
    ((window.gitter = {}).chat = {}).options = {
      room: 'ApocalypseBlog/Apocalypse'
    };
  </script>
  <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<meta name="generator" content="Hexo 5.3.0"></head>
<!--DaoVoice服务http://blog.daovoice.io/-->

    <script>(function (i, s, o, g, r, a, m) {
        i['DaoVoiceObject'] = r;
        i[r] = i[r] ||
          function () {
            (i[r].q = i[r].q || []).push(arguments);
          };
        i[r].l = 1 * new Date();
        a = s.createElement(o);
        m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        a.charset = 'utf-8';
        m.parentNode.insertBefore(a, m);
      })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/b00f5052.js", 'daovoice');
      daovoice('init', {
        app_id: "b00f5052",
      });
      daovoice('update');
    </script>
  
<body>

    <div class="container">
    <header class="header">
        <div class="header-type">
            
            <div class="header-type-inner">
                
                    <a class="header-type-title" href="/">ApocalypseBlog</a>
                
    
                
            </div>
        </div>
        <div class="header-menu">
            <div class="header-menu-inner">
                
            </div>
            <div class="header-menu-social">
                
            </div>
        </div>

        <div class="header-menu-mobile">
            <div class="header-menu-mobile-inner" id="mobile-menu-open">
                <i class="icon icon-menu"></i>
            </div>
        </div>
    </header>

    <div class="header-menu-mobile-menu">
        <div class="header-menu-mobile-menu-bg"></div>
        <div class="header-menu-mobile-menu-wrap">
            <div class="header-menu-mobile-menu-inner">
                <div class="header-menu-mobile-menu-close" id="mobile-menu-close">
                    <i class="icon icon-cross"></i>
                </div>
                <div class="header-menu-mobile-menu-list">
                    
                </div>
            </div>
        </div>
    </div>

</div>

    <div class="container">
        <div class="main">
            <section class="inner">
                <section class="inner-main">
                    <div class="post">
    <article id="post-ckk6o6aui00047kny2jq4cct1" class="article article-type-post" itemscope
    itemprop="blogPost">

    <div class="article-inner">

        
          
        
        
        

        
        <header class="article-header">
            
  
    <h1 class="article-title" itemprop="name">
      FragmentStatePagerAdapter在ViewPager中优化了什么
    </h1>
  

        </header>
        

        <div class="article-more-info article-more-info-post hairline">

            <div class="article-date">
  <time datetime="2020-06-15T22:42:45.000Z" itemprop="datePublished">2020-06-16</time>
</div>

            

            

            

        </div>

        <div class="article-entry post-inner-html hairline" itemprop="articleBody">
            <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>OK，填坑篇的文章来了。</p>
<p>当我打开官方文档准备开始了解FragmentStatePagerAdapter的时候。我仿佛像是…闭关蛰伏数十载，准备反清复明；出关时发现大清已经亡了…</p>
<p><img src="http://user-gold-cdn.xitu.io/2020/5/28/17259fac8a6bcf70?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="img"></p>
<p>什么鬼，我还不会用呢，就tm废弃了？？？</p>
<a id="more"></a>

<h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>当然这不妨碍咱们去了解它如何增强了FragmentPagerAdapter。扶我起来，我还能学！</p>
<p>看FragmentStatePagerAdapter之前，咱们还是要先看<a target="_blank" rel="noopener" href="https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter">文档</a></p>
<p>官网是这么介绍这个类的（我直接用自己蹩脚的英文翻译了一下）：</p>
<p>当存在大量fragment时，此版本的更加高效。当Fragment对用户不可见时，它们的整个Fragment可能会被destory，仅保留该Fragment的状态。与FragmentPagerAdapter相比会占用更少的内存。</p>
<blockquote>
<p>它的用法和FragmentPagerAdapter（以下简称FPA）一模一样，这里就不展开了。大家有兴趣可以直接看文档中的demo。</p>
</blockquote>
<p>从文档介绍来看，FragmentStatePagerAdapter提供更少的内存开销。第二篇文章，咱们也已经明白了FragmentPagerAdapter在FragmentManager体系下会可能出现大量内存消耗的问题。那么咱们就来看看，FragmentStatePagerAdapter是如何优化这个问题。</p>
<h2 id="一、如果做到更少的内存开销？"><a href="#一、如果做到更少的内存开销？" class="headerlink" title="一、如果做到更少的内存开销？"></a>一、如果做到更少的内存开销？</h2><p>FragmentStatePagerAdapter（<strong>以下简称FSPA</strong>）的实现比较的简单，解决方式也很简单粗暴。咱们先看一个关键的方法instantiateItem()，基于这个方法咱们分4步来看一下这里的实现原理：</p>
<pre><code class="java">@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) &#123;
    // 步骤1
    if (mFragments.size() &gt; position) &#123;
        Fragment f = mFragments.get(position);
        if (f != null) &#123;
            return f;
        &#125;
    &#125;
    // 省略代码
    // 步骤2
    Fragment fragment = getItem(position);
    if (mSavedState.size() &gt; position) &#123;
        Fragment.SavedState fss = mSavedState.get(position);
        if (fss != null) &#123;
            fragment.setInitialSavedState(fss);
        &#125;
    &#125;
    // 步骤 3
    while (mFragments.size() &lt;= position) &#123;
        mFragments.add(null);
    &#125;
    // 省略部分代码
    // 步骤4
    mFragments.set(position, fragment);
    mCurTransaction.add(container.getId(), fragment);
    // 省略部分代码
    return fragment;
&#125;</code></pre>
<blockquote>
<p>我们可以看到这里的instantiateItem()和FPA有着极大的不同：这里没有通过FragmentManager去find已经存在的Fragment！这里可以断定FSPA失去了FPA上缓存的逻辑，接下来咱们会通过FSPA的源码来进一步了解二者逻辑上的不同。</p>
</blockquote>
<h3 id="1-1、步骤一分析"><a href="#1-1、步骤一分析" class="headerlink" title="1.1、步骤一分析"></a>1.1、步骤一分析</h3><p>步骤1中的mFragments是Adapter里的局部变量<code>private ArrayList&lt;Fragment&gt; mFragments = new ArrayList&lt;&gt;()</code>，看到着我们第一想法就能够明白FSPA对Fragment的管理，在FragmentManager的基础上包了一层。</p>
<p>这里的处理也很简单粗暴，如果基于position能在mFragments中找到Fragment就直接return。这里有一个点，我们需要注意，这里是<strong>直接return</strong>。也就是意味着被mFragment持有的Fragment实例是没有从FragmentManager中detach的，因此不需要重新走状态。</p>
<p>此外需要留意的一点是：<code>if (f != null)</code>，意味着mFragments里是有可能为null的，所以我们可以猜测mFragments对Fragment也是一个动态变化的持有关系。</p>
<h3 id="1-2、步骤二分析"><a href="#1-2、步骤二分析" class="headerlink" title="1.2、步骤二分析"></a>1.2、步骤二分析</h3><p>很熟悉的方法调用，找不到缓存的Fragment，调getItem()，交给实现方自行初始化Fragment。</p>
<p>然后基于mSavedState对当前Fragment执行一次initSavedState操作。</p>
<p>这里可能有小伙伴会有疑问，新new出来的Fragment为啥有可能会有SavedState呢？</p>
<p>针对这个问题，先简单解释一下（大家可以再后文中得到详细答案）：因为这个mSavedState会存在所有实例过的Fragment的状态，但是mFragments里仅仅会存放当前attach的Fragment。因此调用getItem()时初始化的Fragment是有可能之前初始化过，因此这种case下是要恢复其状态的。</p>
<h3 id="1-3、步骤三分析"><a href="#1-3、步骤三分析" class="headerlink" title="1.3、步骤三分析"></a>1.3、步骤三分析</h3><p>步骤三做的事情就比较有趣了：</p>
<pre><code class="java">while (mFragments.size() &lt;= position) &#123;
    mFragments.add(null);
&#125;</code></pre>
<p>说白了就是在占位。看到这一步，咱们就能明白：mFragments就是一个“以position为key，fragment为value的Map”。</p>
<p>当我们定位到一个很靠后的position时。那么代码走到这我们得到的mFragments的List很有可能是这样的 ：[fragment1,fragment2,null,null,null,接下来要被add的fragment6]</p>
<h3 id="1-4、步骤四分析"><a href="#1-4、步骤四分析" class="headerlink" title="1.4、步骤四分析"></a>1.4、步骤四分析</h3><p>步骤四就很简单了，add我们getItem出来的Fragment。</p>
<p>看完这四步，咱们大概也会发现代码并没有什么难的，虽然我们只看了一个方法，但是基本可以猜出FSPA的原理：</p>
<ul>
<li>只缓存当前attach上的Fragment</li>
<li>缓存所有attach过Fragment的SaveState，以便重新new时的状态恢复</li>
</ul>
<p>看起来是因为缓存的Fragment数量少了所以内存开销变少了…不过我猜有同学这个时候会提出疑问：即使FSPA里mFragments缓存的Fragment少了，但是FragmentStore里该缓存还是要缓存的啊，这么一看，FSPA甚至多缓存了一份！</p>
<p>接下来咱们就要看另一个方法了，看看FSPA如果解决上述的问题。</p>
<h2 id="二、销毁Fragment"><a href="#二、销毁Fragment" class="headerlink" title="二、销毁Fragment"></a>二、销毁Fragment</h2><p>其实有了第二篇文章的分析，咱们已经明确是FragmentManager内存爆炸的原因就是在于FragmentStore在mActive中强引用了所有的Fragment实例，不进行任何回收。</p>
<p>既然FSPA号称更少的开销，那么势必要直面这个问题。所以接下来就让咱们看看，FSPA销毁Fragment的策略。</p>
<h3 id="2-1、destroyItem"><a href="#2-1、destroyItem" class="headerlink" title="2.1、destroyItem()"></a>2.1、destroyItem()</h3><p>FSPA和FPA主要区别就在于对destroyItem()的实现。这里咱们先对比一下二者的实现：</p>
<pre><code class="java">// FSPA
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) &#123;
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) &#123;
        mCurTransaction = mFragmentManager.beginTransaction();
    &#125;
    while (mSavedState.size() &lt;= position) &#123;
        mSavedState.add(null);
    &#125;
    mSavedState.set(position, fragment.isAdded()
            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
    mFragments.set(position, null);
    // 注意这里
    mCurTransaction.remove(fragment);
    if (fragment.equals(mCurrentPrimaryItem)) &#123;
        mCurrentPrimaryItem = null;
    &#125;
&#125;

// FPA
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) &#123;
    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) &#123;
        mCurTransaction = mFragmentManager.beginTransaction();
    &#125;
    // 注意这里
    mCurTransaction.detach(fragment);
    if (fragment.equals(mCurrentPrimaryItem)) &#123;
        mCurrentPrimaryItem = null;
    &#125;
&#125;</code></pre>
<p>FSPA调用了remove方法，而FPA调用的是detach方法。接下来咱们就看看这二者有什么不同。其实无论是remove还是detach都会走到executeOps()中的switch判断：</p>
<pre><code class="java"> case OP_REMOVE:
    f.setNextAnim(op.mExitAnim);
    mManager.removeFragment(f);
    break;

case OP_DETACH:
    f.setNextAnim(op.mExitAnim);
    mManager.detachFragment(f);
    break;</code></pre>
<p>但是这里无论是removeFrament()还是detachFragment()。本质调的都是<code>mFragmentStore.removeFragment(fragment);</code>，这里是把当前Fragment从FragmentStore中的mAdded列表移除还不会动mActive列表。</p>
<p>因此对于FSPA来说，它并不是通过这种方式来控制内存开销。咱们继续往下看…</p>
<h3 id="2-2、控制Fragment的状态机"><a href="#2-2、控制Fragment的状态机" class="headerlink" title="2.2、控制Fragment的状态机"></a>2.2、控制Fragment的状态机</h3><p>上述switch判断结束后，才会走到真正驱动状态的地方：</p>
<pre><code class="java">if (!mReorderingAllowed &amp;&amp; op.mCmd != OP_ADD &amp;&amp; f != null) &#123;
    // 这里边会走到moveToState()中
    mManager.moveFragmentToExpectedState(f);
&#125;

FragmentManager#moveToState()

if (f.mState &lt;= newState) &#123;
    switch (f.mState) &#123;
        case Fragment.INITIALIZING:&#123;
            if (newState &gt; Fragment.INITIALIZING) &#123;
                // 省略部分代码
            &#125;
        &#125;
        case Fragment.ATTACHED:&#123;
            // 省略部分代码
        &#125;
        // 省略部分代码
&#125;else if (f.mState &gt; newState) &#123;
    switch (f.mState) &#123;
        case Fragment.RESUMED:
            if (newState &lt; Fragment.RESUMED) &#123;
                // 省略部分代码
            &#125;
        case Fragment.CREATED:
            if (newState &lt; Fragment.CREATED) &#123;
                // 重点在这
                boolean beingRemoved = f.mRemoving &amp;&amp; !f.isInBackStack();
                if (beingRemoved || mNonConfig.shouldDestroy(f)) &#123;
                    makeInactive(fragmentStateManager);
                &#125;
                // 省略部分代码
            &#125;
        // 省略部分代码
    &#125;
    // 省略部分代码
&#125;</code></pre>
<p>这里状态机的逻辑，大家有兴趣可以自己阅读一下。这里处理状态的逻辑还是挺“骚”的。咱们只关注<code>makeInactive()</code>。上文我们之后remove和detach的区别，而这个区别的分水岭就在于这个方法。remove是会走到这个方法中：</p>
<pre><code class="java">private void makeInactive(@NonNull FragmentStateManager fragmentStateManager) &#123;
    // 省略部分代码
    mFragmentStore.makeInactive(fragmentStateManager);
    removeRetainedFragment(f);
&#125;

void makeInactive(@NonNull FragmentStateManager newlyInactive) &#123;
    Fragment f = newlyInactive.getFragment();
    for (FragmentStateManager fragmentStateManager : mActive.values()) &#123;
        if (fragmentStateManager != null) &#123;
            Fragment fragment = fragmentStateManager.getFragment();
            if (f.mWho.equals(fragment.mTargetWho)) &#123;
                fragment.mTarget = f;
                fragment.mTargetWho = null;
            &#125;
        &#125;
    &#125;

    mActive.put(f.mWho, null);

    if (f.mTargetWho != null) &#123;
        f.mTarget = findActiveFragment(f.mTargetWho);
    &#125;
&#125;</code></pre>
<p>可以看到<code>makeInactive()</code>方法中会对mActive进行回收的操作。因此FSPA比FPA的优化就在于移除掉了对mActive中“不必要”的引用。</p>
<p>我猜看到这大家应该就能够get到FSPA的优化点，不过…问题来了：既然把FragmentManager中mActive移除掉了，那我们的缓存呢？</p>
<h2 id="三、失去了缓存"><a href="#三、失去了缓存" class="headerlink" title="三、失去了缓存"></a>三、失去了缓存</h2><p>事实的确如此，咱们在开篇看<code>instantiateItem()</code>实现的时候就已经发现，FSPA移除了通过FragmentManager去find缓存的逻辑。</p>
<p>咱们基于之前的文章，可以明白FPA的缓存是基于FragmentManager的mActive缓存，也明白FPA内存溢出也是因为FragmentManager的mActive缓存。</p>
<p>因此FSPA的优化原理也很好理解，在FragmentManager中移除掉了mActive的缓存。</p>
<p>这里也就意味着，FSPA和FPA有一些不同：</p>
<ul>
<li>1、只要不在mAdd的Fragment，FSPA都会走getItem()去new Fragment。</li>
<li>2、我们没办法方便的基于FragmentManager去拿到我们想要得到的Fragment实例。（FSPA是基于id去把Fragment添加到mAdd）</li>
</ul>
<h3 id="3-1、ViewPager中取特定Fragment实例是否合理"><a href="#3-1、ViewPager中取特定Fragment实例是否合理" class="headerlink" title="3.1、ViewPager中取特定Fragment实例是否合理"></a>3.1、ViewPager中取特定Fragment实例是否合理</h3><p>这里咱们多聊一句。不知道大家有没有发现，无论上FPA还是FSPA，Google都没有主动提供获取内部持有Fragment的public方法。甚至在FSPA中，移除了任何这种操作的可能行。</p>
<p>如果单纯从这个现象来看，基于ViewPager去变相的获取内部Fragment是一个“不合理”的操作。但是咱们也很清楚需求这种东西，如果都“合理”那就不叫需求了…因此这种操作是无法避免的。所有，咱们需要从FSPA和FPA的不同点来明确咱们该用谁…</p>
<ul>
<li>如果我们需要FragmentManager去缓存我们的Fragment那么FPA是一个不错的选择。</li>
<li>如果我们拥有大量的Fragment在ViewPager中，那么FSPA是一个不错的选择。</li>
</ul>
<blockquote>
<p>当然鉴于FSPA已经被废弃了，咱们项目中首选还是ViewPager2。关于ViewPager2的分析会在后续放出…</p>
</blockquote>
<h1 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h1><p>尽可能的学的深入，尽可能的发布正确的文章。欢迎大家评论区一起讨论~</p>

        </div>

    </div>

    

    

    

    

    

    
<nav class="article-nav">
  
    <a href="/2020/06/28/%E8%81%8A%E8%81%8A-Android-%E5%BC%80%E5%8F%91%E7%9A%84%E7%8E%B0%E7%8A%B6%E5%92%8C%E6%80%9D%E8%80%83/" id="article-nav-newer" class="article-nav-link-wrap">
      <div class="article-nav-caption">下一篇</div>
      <div class="article-nav-title">
        
          聊聊 Android 开发的现状和思考
        
      </div>
    </a>
  
  
    <a href="/2020/06/16/100%E8%A1%8Cjs%E4%BB%A3%E7%A0%81%E4%B8%8B%E8%BD%BD%E6%8A%96%E9%9F%B3%E6%97%A0%E6%B0%B4%E5%8D%B0%E8%A7%86%E9%A2%91/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-caption">上一篇</div>
      <div class="article-nav-title">100 行 js 代码下载抖音无水印视频</div>
    </a>
  
</nav>


    <section class="share">
        <div class="share-title">分享</div>
        <a class="share-item" target="_blank"
            href="https://twitter.com/share?text=FragmentStatePagerAdapter在ViewPager中优化了什么 - ApocalypseBlog&url=http://example.com/2020/06/16/FragmentStatePagerAdapter%E5%9C%A8ViewPager%E4%B8%AD%E4%BC%98%E5%8C%96%E4%BA%86%E4%BB%80%E4%B9%88/">
            <box-icon type='logo' name='twitter'></box-icon>
        </a>
        <a class="share-item" target="_blank"
            href="https://www.facebook.com/sharer.php?title=FragmentStatePagerAdapter在ViewPager中优化了什么 - ApocalypseBlog&u=http://example.com/2020/06/16/FragmentStatePagerAdapter%E5%9C%A8ViewPager%E4%B8%AD%E4%BC%98%E5%8C%96%E4%BA%86%E4%BB%80%E4%B9%88/">
            <box-icon name='facebook-square' type='logo' ></box-icon>
        </a>
        <!-- <a class="share-item" target="_blank"
            href="https://service.weibo.com/share/share.php?title=FragmentStatePagerAdapter在ViewPager中优化了什么 - ApocalypseBlog&url=http://example.com/2020/06/16/FragmentStatePagerAdapter%E5%9C%A8ViewPager%E4%B8%AD%E4%BC%98%E5%8C%96%E4%BA%86%E4%BB%80%E4%B9%88/&pic=">
            <div class="n-icon n-icon-weibo"></div>
        </a> -->
    </section>

</article>









</div>
                </section>
            </section>

             
            <aside class="sidebar">
            
                
            </aside>
        </div>
    </div>

    <footer class="footer">
    <div class="footer-wave">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="#3c4859" fill-opacity="1" d="M0,160L60,181.3C120,203,240,245,360,240C480,235,600,181,720,186.7C840,192,960,256,1080,261.3C1200,267,1320,213,1380,186.7L1440,160L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path></svg>
    </div>

    <div class="footer-wrap">
        <div class="footer-inner"> 
            ApocalypseBlog &copy; 2021<br>
            Powered By Hexo · Theme By <a href="https://github.com/lh1me/hexo-theme-aomori" target="_blank">Aomori</a>
        </div>
    </div>

</footer>




<script src="/dist/build.js?1.14.0.js"></script>


<script src="/dist/custom.js?1.14.0.js"></script>









</body>

</html>